﻿#region Copyright © 2011 Prageeth Silva <prageeth@thenewcoders.com>
/*
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the author(s) be held liable for any damages arising from
 * the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 *   1. The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software
 *      in a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 * 
 *   2. Altered source versions must be plainly marked as such, and must not
 *      be misrepresented as being the original software.
 * 
 *   3. This notice may not be removed or altered from any source distribution.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Text.RegularExpressions;

namespace SourcePlus.RtfPdf
{
    public class RtfParser
    {

        private Dictionary<int, Font> FontTable { get; set; }

        private Dictionary<int, Color> ColorTable { get; set; }
        
        private static Regex rgxFonts = new Regex(@"((\\f\d+[\w\\]+\s)((?<!\\\w+0)[\w\s]+))(?=;)");

        private static Regex rgxColors = new Regex(@"\\red(\d+)\\green(\d+)\\blue(\d+);");

        private static Regex rgxReplacements = new Regex(@"(\\{)|(\\})|(\\pard)");
                        
        public RtfParser()
        {

            this.FontTable = null;
            this.ColorTable = null;

        }
        
        public RtfObject Parse(string rtf)
        {
                        
            string[] lines = rtf.Split(new string[] { "\\par\r\n" }, StringSplitOptions.None);

            // find the header and body sections
            int start = 0;
            while (start < lines.Length) 
            {

                if (lines[start].StartsWith(@"\c"))
                {
                    break;
                }
                else if (lines[start].Contains("\\viewkind"))
                {
                    // next line, increment once more
                    start++;
                    break;
                }
                start++;

            }

            // build header as one string
            StringBuilder sb = new StringBuilder();
            for (int i=0; i < start; i++)
            {
                sb.Append(lines[i]);
                sb.Append('\n');
            }
            string header = sb.ToString();

            // extract font & color tables
            this.FontTable = this.ExtractFontTable(header);
            this.ColorTable = this.ExtractColorTable(header);

            // document wide variables
            Font font = this.FontTable[0];
            Color color = this.ColorTable[0];
            
            // parse the body
            int len = lines.Length, last = len-1;
            List<RtfLine> rtfLines = new List<RtfLine>(len);
            for (int i = start; i < len; i++)
            {

                string rawLine = lines[i];

                // remove the last RTF tag brace
                if (i == last)
                {
                    int brace = rawLine.LastIndexOf('}');
                    if (brace >= 0)
                    {
                        rawLine = rawLine.Remove(brace, 1);
                    }
                }

                RtfLine line = this.ExtractRtfLine(rgxReplacements.Replace(rawLine,
                                                        this.ReplaceBraceTags),
                                                        ref font, ref color);
                if (line != null)
                {
                    rtfLines.Add(line);
                }

            }

            return new RtfObject(rtfLines.ToArray());

        }
        
        protected string ReplaceBraceTags(Match match)
        {

            if (match.Groups[1].Success) // '\{'
            {
                return "{";
            }
            if (match.Groups[2].Success) // '\}'
            {
                return "}";
            }
            if (match.Groups[3].Success) // '\pard'
            {
                return string.Empty;
            }
            return string.Empty;

        }

        private Dictionary<int, Font> ExtractFontTable(string header)
        {
            
            MatchCollection matches = rgxFonts.Matches(header);
            Dictionary<int, Font> fonts = new Dictionary<int, Font>(matches.Count);

            int i = 0;
            foreach (Match m in matches)
            {
                if (m.Groups[3].Success)
                {
                    string font = m.Groups[3].Value;
                    fonts.Add(i, new Font(font, 9));
                }
                i++;
            }

            return fonts;

        }

        private Dictionary<int, Color> ExtractColorTable(string header)
        {
                        
            MatchCollection matches = rgxColors.Matches(header);
            Dictionary<int, Color> colors = new Dictionary<int, Color>(matches.Count);

            int r,g,b;
            colors.Add(0, Color.Black);
            int i = 1;
            foreach (Match m in matches)
            {
                if (m.Groups[1].Success && m.Groups[2].Success && m.Groups[3].Success)
                {
                    if (Int32.TryParse(m.Groups[1].Value, out r)
                        && Int32.TryParse(m.Groups[2].Value, out g)
                        && Int32.TryParse(m.Groups[3].Value, out b))
                    {
                        colors.Add(i, Color.FromArgb(r,g,b));
                    }
                }
                i++;
            }

            return colors;

        }

        private RtfLine ExtractRtfLine(string line, ref Font font, ref Color color)
        {

            Regex rgxFormat = new Regex(@"(\\f\d+\s?)?(\\cf\d+\s?)(\\b\d*\s?)?$?");

            string[] splits = rgxFormat.Split(line);
            List<RtfToken> tokens = new List<RtfToken>(splits.Length);

            int len = splits.Length;
            for (int i = 0; i < len; i++)
            {
                if (!this.IsFormattingTag(splits[i], ref font, ref color))
                {
                    if (splits[i].Length > 0)
                    {
                        tokens.Add(new RtfToken(splits[i], color, font));
                    }
                }
            }

            return new RtfLine(tokens.ToArray());

        }

        private bool IsFormattingTag(string value, ref Font font, ref Color color)
        {

            if (value.Length > 1 && value[0] == '\\')
            {

                try
                {

                    // color?
                    if (value[1] == 'c')
                    {
                        // format: \cf#  where # is a number
                        int idx;
                        if (Int32.TryParse(value.Substring(3), out idx))
                        {
                            color = this.ColorTable[idx];
                        }
                        return true;
                    }
                    // font?
                    else if (value[1] == 'f')
                    {
                        // format: \f#  where # is a number
                        int idx;
                        if (Int32.TryParse(value.Substring(2), out idx))
                        {
                            font = this.FontTable[idx];
                        }
                        return true;
                    }
                    // font style?
                    else if (value[1] == 'b')
                    {
                        // '\b' => bold ON
                        if (value.Length <= 2 || value[2] == ' ')
                        {
                            font = new Font(font, FontStyle.Bold);
                        }
                        // '\b0' => bold OFF
                        else if (value[2] == '0')
                        {
                            font = new Font(font, FontStyle.Regular);
                        }
                        // unknown
                        else
                        {
                            return false;
                        }
                        return true;
                    }

                }
                catch
                {
                    return false;
                }

                return true;

            }

            return false;

        }

    }
    
    public class RtfObject
    {

        public RtfLine[] Lines { get; set; }

        public RtfObject()
        {
            this.Lines = null;
        }

        public RtfObject(RtfLine[] lines)
        {
            this.Lines = lines;
        }

    }

    public class RtfLine
    {

        public RtfToken[] Tokens { get; set; }

        public RtfLine()
        {
            this.Tokens = null;
        }

        public RtfLine(RtfToken[] tokens)
        {
            this.Tokens = tokens;
        }

        public RtfLine(RtfToken token)
        {
            this.Tokens = new RtfToken[] { token };
        }
        
    }

    public class RtfToken
    {

        public string Value { get; set; }

        public Color Color { get; set; }

        public Font Font { get; set; }

        public RtfToken(string value, Font font)
        {
            this.Value = value;
            this.Color = Color.Black;
            this.Font = Font;
        }

        public RtfToken(string value, Color color, Font font)
        {
            this.Value = value;
            this.Color = color;
            this.Font = font;
        }

    }

}
